package com.qianxun.framework.common.util;

import javax.net.ssl.*;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * http工具
 *
 * @author huwei 3313749341@qq.com
 *
 */
public class HttpUtil {

//	public static void main(String[] args) {
//		//示例1
//		try {
//			HttpResponse response = HttpUtil.get("http://www.baidu.com");
//			System.out.println(new String(response.getResponse()));
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//		//示例2 , 流式调用
//		try {
//			HttpResponse response = HttpUtil.connection("http://www.baidu.com")
//					.addHeader("Content-type", "application/json")
//					.setHeader("gzip", "on").setParameter("username", "test").setParameter("pwd", "123").post();
//
//			if(response.getStatus() == 200) {
//				String encode = response.getHeader("encode");
//				if(encode == null) {
//					encode = "utf-8";
//				}
//				System.out.println(new String(response.getResponse() ,encode));
//			}
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//	}

    /**
     * 不可实例,使用类对象调用其方法
     */
    private HttpUtil() {}

    /**
     * 使用new URL("https://....").openConnection()
     * 方法时,其访问服务是基于https协议,openConnection()方法将在第一次调用耗费
     * 大量的时间,其时间固定在2s左右,预计是URL内部初始化资源,其具体如何,因为没有查看到sun.net.www.protocol.http包下的源码,所以无法得知,
     * 知道的朋友可告知作者;
     */
    public static final void init() {
        try {
            get("https://www.baidu.com");
        } catch (IOException e) {
        }
    }

    /**
     * 创建一个http连接 ,并通过该连接请求资源
     *
     * @param url 请求地址
     * @return
     */
    public static HttpConnection connection(String url) {
        return new HttpConnection(url);
    }

    public static HttpResponse get(String url) throws IOException {
        return get(url, null);
    }

    public static HttpResponse get(String url, Integer connectionTimeout) throws IOException {
        return get(url, connectionTimeout, null);
    }

    public static HttpResponse get(String url, Integer connectionTimeout, Map<String, String> parameters)
            throws IOException {
        return connection(url).setConnectionTimeout(connectionTimeout).setParameter(parameters).get();
    }

    public static HttpResponse post(String url) throws IOException {
        return post(url, null);
    }

    public static HttpResponse post(String url, Integer connectionTimeout) throws IOException {
        return post(url, connectionTimeout, null);
    }

    public static HttpResponse post(String url, Integer connectionTimeout, Map<String, String> parameters)
            throws IOException {
        return connection(url).setConnectionTimeout(connectionTimeout).setParameter(parameters).post();
    }

    /**
     * http请求连接处理对象
     *
     * @author huwei 3313749341@qq.com
     */
    public static final class HttpConnection {
        // 默认编码
        private static final String ENCODE = "UTF-8";
        // 请求体,存储设置请求参数
        private HttpRequest httpRequest;

        public HttpConnection(String url) {
            this.httpRequest = new HttpRequest(url);
        }

        public HttpConnection setRequestMethod(RequestMethod method) {
            httpRequest.setMethod(method);
            return this;
        }

        public HttpConnection setConnectionTimeout(Integer connectionTimeout) {
            httpRequest.setConnectionTimeout(connectionTimeout);
            return this;
        }

        /**
         * 设置接受响应超时时间
         *
         * @param readTimeout 响应超时时间
         * @return
         */
        public HttpConnection setReadTimeout(Integer readTimeout) {
            httpRequest.setReadTimeout(readTimeout);
            return this;
        }

        /**
         * 设置是否允许自动重定向
         *
         * @param isRedirect 是否允许自动重定向
         * @return
         */
        public HttpConnection setRedirect(Boolean isRedirect) {
            httpRequest.setRedirect(isRedirect);
            return this;
        }

        public HttpConnection setUseCache(Boolean useCache) {
            httpRequest.setUseCache(useCache);
            return this;
        }

        /**
         * 设置请求头,该方法将覆盖同名属性的值
         *
         * @param header
         * @param value
         * @return
         */
        public HttpConnection setHeader(String header, String value) {
            setHeader(header, Arrays.asList(value));
            return this;
        }

        /**
         * 设置请求头,该方法将覆盖同名属性的值
         *
         * @param header
         * @param value
         * @return
         */
        public HttpConnection setHeader(String header, List<String> value) {
            httpRequest.getHeaders().put(header, value);
            return this;
        }

        /**
         * 设置请求头,该方法将覆盖同名属性的值
         *
         * @param headers 请求属性的map
         * @return
         */
        public HttpConnection setHeader(Map<String, String> headers) {
            if (headers != null) {
                headers.forEach((k, v) -> {
                    setHeader(k, v);
                });
            }
            return this;
        }

        /**
         * 设置请求头,该方法将覆盖同名属性的值
         *
         * @param headers 请求属性的map
         * @return
         */
        public HttpConnection setHeaderMore(Map<String, List<String>> headers) {
            if (headers != null) {
                headers.forEach((k, v) -> {
                    setHeader(k, v);
                });
            }
            return this;
        }

        /**
         * 设置请求头,该方法不会覆盖现有的同名属性的值,会追加到该属性后面
         *
         * @param header 属性
         * @param value  值
         * @return
         */
        public HttpConnection addHeader(String header, String value) {
            Map<String, List<String>> headers = httpRequest.getHeaders();
            List<String> old = headers.get(header);
            if (old == null) {
                old = new ArrayList<String>(16);
            }
            old.add(value);
            headers.put(header, old);
            return this;
        }

        /**
         * 添加请求参数,该方法将为该请求添加多个相同名称参数,其格式类似于:&a=1&a=2&a=3
         *
         * @param parameter 参数名
         * @param value    参数值
         * @return
         */
        public HttpConnection addParameter(String parameter, String value) {
            Map<String, List<String>> paramters = httpRequest.getParameters();
            List<String> old = paramters.get(parameter);
            if (old == null) {
                old = new ArrayList<String>(16);
            }
            old.add(value);
            paramters.put(parameter, old);
            return this;
        }

        /**
         * 用于为post请求方式设置参数 注意:当使用此方式设置参数后,使用addParameter或setParameter方法设置的参数将不被生效;
         *
         * @param content
         * @return
         */
        public HttpConnection setPostParameter(byte[] content) {
            httpRequest.setParameterConent(content);
            return this;
        }

        /**
         * 设置请求参数,该方法将覆盖现有的参数值
         *
         * @param parameter 参数名
         * @param value     参数值
         * @return
         */
        public HttpConnection setParameter(String parameter, String value) {
            setParameter(parameter, Arrays.asList(value));
            return this;
        }

        /**
         * 设置请求参数,该方法将覆盖现有的参数值
         *
         * @param parameter 参数名
         * @param value     参数值
         * @return
         */
        public HttpConnection setParameter(String parameter, List<String> value) {
            httpRequest.getParameters().put(parameter, value);
            return this;
        }

        /**
         * 设置请求参数,该方法将覆盖现有的参数值
         *
         * @return
         */
        public HttpConnection setParameter(Map<String, String> parameters) {
            if (parameters != null) {
                parameters.forEach((k, v) -> {
                    setParameter(k, v);
                });
            }
            return this;
        }

        /**
         * 设置请求参数,该方法将覆盖现有的参数值
         *
         * @return
         */
        public HttpConnection setParameterMore(Map<String, List<String>> parameters) {
            if (parameters != null) {
                parameters.forEach((k, v) -> {
                    setParameter(k, v);
                });
            }
            return this;
        }

        public HttpConnection setEncode(String encode) {
            httpRequest.setEncode(encode);
            return this;
        }

        public HttpResponse execute() throws IOException {
            checkUrlLegal();
            // https请求专属参数设置
            if (isHttps()) {
                setHttpsParameters();
            }
            String url = httpRequest.getUrl();
            // get参数设置
            if (RequestMethod.GET.name().equals(getMethod())) {
                // 设置请求参数
                url = setRequestParametersForMethodGet(url);
            }
            // 创建并打开链接
            URL urlObj = new URL(encodeUrl(url));
            HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
            // 属性设置
            setHeaders(connection);
            if (!RequestMethod.GET.name().equals(getMethod())) {
                // 设置请求方式,默认为get
                connection.setRequestMethod(getMethod());
                // 设置请求参数
                setRequestParametersForMethodPost(connection);
            }
            // 建立连接
            connection.connect();
            // 获取响应信息
            byte[] response = readAndDisconnect(connection);
            return new HttpResponse(connection, response);
        }

        public HttpResponse get() throws IOException {
            httpRequest.setMethod(RequestMethod.GET);
            return execute();
        }

        public HttpResponse post() throws IOException {
            httpRequest.setMethod(RequestMethod.POST);
            return execute();
        }

        private String getMethod() {
            return httpRequest.getMethod() == null ? RequestMethod.GET.name() : httpRequest.getMethod().name();
        }

        private boolean isHttps() {
            return httpRequest.getUrl() != null && httpRequest.getUrl().startsWith("https");
        }

        private boolean isHttp() {
            return httpRequest.getUrl() != null && httpRequest.getUrl().startsWith("http");
        }

        private String getEncode() {
            return httpRequest.getEncode() == null ? ENCODE : httpRequest.getEncode();
        }

        /**
         * 处理基于https协议请求的ssl认证问题,默认采用空实现,不做任何处理
         */
        private void setHttpsParameters() {
            SSLContext sslcontext = null;
            try {
                sslcontext = SSLContext.getInstance("SSL", "SunJSSE");
                try {
                    sslcontext.init(null, new TrustManager[] { new NUllX509TrustManager() },
                            new java.security.SecureRandom());
                } catch (KeyManagementException e) {
                    // TODO Auto-generated catch block
                }
                HostnameVerifier ignoreHostnameVerifier = new HostnameVerifier() {
                    public boolean verify(String s, SSLSession sslsession) {
                        System.out.println("WARNING: Hostname is not matched for cert.");
                        return true;
                    }
                };
                HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
                HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());
            } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
                // TODO Auto-generated catch block
            }
        }

        private static String encodeUrl(String url) {
            try {
                URL u = new URL(url);
                return encodeUrl(u).toExternalForm();
            } catch (Exception e) {
                return url;
            }
        }

        private static URL encodeUrl(URL u) {
            try {
                String urlS = u.toExternalForm();
                urlS = urlS.replaceAll(" ", "%20");
                final URI uri = new URI(urlS);
                return new URL(uri.toASCIIString());
            } catch (Exception e) {
                return u;
            }
        }

        private static byte[] readAndDisconnect(HttpURLConnection connection) throws IOException {
            connection.connect();
            ByteArrayOutputStream res = new ByteArrayOutputStream();
            InputStream is = connection.getInputStream();
            int len;
            byte[] buf = new byte[1024];
            while ((len = is.read(buf, 0, buf.length)) != -1) {
                res.write(buf, 0, len);
            }
            is.close();
            res.close();
            connection.disconnect();
            return res.toByteArray();
        }

        private void setHeaders(HttpURLConnection connection) {
            // 连接超时设置
            if (httpRequest.getConnectionTimeout() != null) {
                connection.setConnectTimeout(httpRequest.getConnectionTimeout());
            }
            // 读取超时设置
            if (httpRequest.getReadTimeout() != null) {
                connection.setReadTimeout(httpRequest.getReadTimeout());
            }
            // 重定向设置
            if (httpRequest.getRedirect() != null) {
                connection.setInstanceFollowRedirects(httpRequest.getRedirect());
            }
            // 缓存设置
            if (httpRequest.getUseCache() != null) {
                connection.setUseCaches(httpRequest.getUseCache());
            }
            // 请求头的属性设置
            httpRequest.getHeaders().forEach((k, v) -> {
                StringBuffer sb = new StringBuffer();
                v.forEach((e) -> {
                    sb.append(",").append(e);
                });
                String value = sb.length() > 0 ? sb.deleteCharAt(0).toString() : "";
                connection.setRequestProperty(k, value);
            });
        }

        private void setRequestParametersForMethodPost(HttpURLConnection connection) throws IOException {
            connection.setDoOutput(true);
            byte[] content = httpRequest.getParameterConent() == null ? wrapRequestParameter().getBytes(getEncode())
                    : httpRequest.getParameterConent();
            connection.setRequestProperty("Content-Length", String.valueOf(content.length));
            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            out.write(content);
            out.flush();
            out.close();
        }

        private String setRequestParametersForMethodGet(String url) {
            return url += new StringBuffer("?").append(wrapRequestParameter()).toString();
        }

        private String wrapRequestParameter() {
            StringBuffer sb = new StringBuffer();
            httpRequest.getParameters().forEach((k, v) -> {
                v.forEach((e) -> {
                    sb.append("&").append(k).append("=").append(e);
                });
            });
            return sb.length() == 0 ? "" : sb.deleteCharAt(0).toString();
        }

        private void checkUrlLegal() {
            if (!(isHttps() || isHttp())) {
                throw new RuntimeException("url illegal ,the url be start with http or https!");
            }
        }

    }

    public static class HttpResponse {
        private HttpURLConnection connection;
        private byte[] response;

        public HttpResponse(HttpURLConnection connection, byte[] response) {
            super();
            this.connection = connection;
            this.response = response;
        }

        public String getHeader(String name) {
            return connection.getHeaderField(name);
        }

        public Map<String, List<String>> getHeaderFileds() {
            return connection.getHeaderFields();
        }

        public byte[] getResponse() {
            return response;
        }

        public int getStatus() {
            try {
                return connection.getResponseCode();
            } catch (IOException e) {
            }
            return -1;
        }
    }

    public static enum RequestMethod {
        GET, POST, PUT, DELETE;
    }

    private static final class HttpRequest {
        // 请求地址
        private String url;
        private RequestMethod method;
        // 连接超时时间
        private Integer connectionTimeout;
        // 读取超时时间
        private Integer readTimeout;
        // 重定向
        private Boolean redirect;
        // 使用缓存
        private Boolean useCache;
        // 编码
        private String encode;
        // 请求头属性
        private Map<String, List<String>> headers = new HashMap<String, List<String>>(16);
        // 请求参数
        private Map<String, List<String>> parameters = new HashMap<String, List<String>>(16);
        // 参数字节数组,该参数不为空时,将不解析parameters字段的内容
        // 该属性的设置,主要为解决,某些加密后的数据,不能在转换为string类型
        private byte[] parameterConent;

        public HttpRequest(String url) {
            this.url = url;
        }

        public Integer getConnectionTimeout() {
            return connectionTimeout;
        }

        public void setConnectionTimeout(Integer connectionTimeout) {
            this.connectionTimeout = connectionTimeout;
        }

        public Integer getReadTimeout() {
            return readTimeout;
        }

        public void setReadTimeout(Integer readTimeout) {
            this.readTimeout = readTimeout;
        }

        public Boolean getRedirect() {
            return redirect;
        }

        public void setRedirect(Boolean redirect) {
            this.redirect = redirect;
        }

        public Boolean getUseCache() {
            return useCache;
        }

        public void setUseCache(Boolean useCache) {
            this.useCache = useCache;
        }

        public Map<String, List<String>> getHeaders() {
            return headers;
        }

        public Map<String, List<String>> getParameters() {
            return parameters;
        }

        public String getEncode() {
            return encode;
        }

        public void setEncode(String encode) {
            this.encode = encode;
        }

        public byte[] getParameterConent() {
            return parameterConent;
        }

        public void setParameterConent(byte[] parameterConent) {
            this.parameterConent = parameterConent;
        }

        public RequestMethod getMethod() {
            return method;
        }

        public void setMethod(RequestMethod method) {
            this.method = method;
        }

        public String getUrl() {
            return url;
        }
    }

    /**
     * X509TrustManager接口的空实现,未做任何处理 用于处理客户提交的基于https协议的ssl请求,
     *
     * @author huwei
     */
    private static class NUllX509TrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }
}
